﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace DynamicGeometry
{
    /// <summary>
    /// Any set of IFigures
    /// </summary>
    public partial class FigureList : CollectionWithEvents<IFigure>, IFigureList
    {
        public FigureList()
        {
        }

        public FigureList(params IFigure[] figures) : this((IEnumerable<IFigure>)figures)
        {
        }

        public FigureList(IEnumerable<IFigure> existingListToClone) : this()
        {
            this.AddRange(existingListToClone);
        }

        IFigure IFigure.Clone()
        {
            FigureList result = new FigureList();
            foreach (var figure in this)
            {
                result.Add(figure.Clone());
            }
            return result;
        }

        public IFigure this[string index]
        {
            get { return this
                .GetAllFiguresRecursive()
                .Where(f => f.Name == index)
                .FirstOrDefault(); }
        }

        public void ApplyStyle()
        {
            this.ForEach(f => f.ApplyStyle());
        }

        private Drawing mDrawing = null;
        public Drawing Drawing
        {
            get
            {
                return mDrawing;
            }
            set
            {
                if (mDrawing == value)
                {
                    return;
                }
                mDrawing = value;
                if (mDrawing != null)
                {
                    foreach (var item in this)
                    {
                        item.Drawing = mDrawing;
                    }
                }
            }
        }

        public void Add(params IFigure[] figures)
        {
            foreach (var figure in figures)
            {
                if (figure.Drawing == null)
                {
                    figure.Drawing = this.Drawing;
                }
                base.Add(figure);
            }
        }

        public void Remove(params IFigure[] figures)
        {
            foreach (var figure in figures)
            {
                base.Remove(figure);
            }
        }

        public void Remove(IEnumerable<IFigure> figures)
        {
            foreach (var figure in figures.ToArray())
            {
                base.Remove(figure);
            }
        }

        public void ClearSelection()
        {
            foreach (var figure in this)
            {
                if (figure.Selected)
                {
                    figure.Selected = false;
                }
            }
        }

        public void EnableAll()
        {
            foreach (var figure in this)
            {
                figure.Enabled = true;
            }
        }

        public bool Exists
        {
            get
            {
                return this.Exists();
            }
            set
            {
                throw new InvalidOperationException("Cannot set the Exists property on a list of figures");
            }
        }

        public virtual IEnumerable<IFigure> Dependencies { get; set; }

        private IFigureList mDependents;
        public virtual IFigureList Dependents
        {
            get
            {
                if (mDependents == null)
                {
                    mDependents = new FigureList();
                }
                return mDependents;
            }
            protected set
            {
                mDependents = value;
            }
        }

        public virtual void UpdateExistence()
        {
            this.ForEach(f => f.UpdateExistence());
        }

        public virtual void Recalculate()
        {
            this.ForEach(f => f.Recalculate());
        }

        public void UpdateVisual()
        {
            foreach (var figure in this)
            {
                if (figure.Exists)
                {
                    figure.UpdateVisual();
                }
            }
        }

        #region HitTest

        /// <summary>
        /// Finds any figure at the point
        /// </summary>
        /// <param name="point">Hittest coordinates</param>
        /// <returns>A figure with topmost ZIndex or null if nothing found</returns>
        public IFigure HitTest(System.Windows.Point point)
        {
            return HitTest(point, figure => figure.Visible);
        }

        /// <summary>
        /// Finds a figure of a given type at the point
        /// </summary>
        /// <param name="point">Coordinates</param>
        /// <param name="figureType">A type (usually typeof(IPoint), typeof(ILine) or typeof(ICircle)
        /// but could be anything)</param>
        /// <returns>A figure or null if nothing was found</returns>
        public IFigure HitTest(Point point, Type figureType)
        {
            return HitTest(point, figure =>
                figure != null && figureType.IsAssignableFrom(figure.GetType()));
        }

        /// <summary>
        /// Finds a figure of a given type at the point
        /// </summary>
        /// <typeparam name="T">A type (usually IPoint, ILine or ICircle
        /// but could be anything)</typeparam>
        /// <param name="point">Coordinates</param>
        /// <returns>A figure or null if nothing was found</returns>
        public T HitTest<T>(Point point)
        {
            return (T)HitTest(point, typeof(T));
        }

        /// <summary>
        /// Finds a figure at a point
        /// </summary>
        /// <param name="point">Coordinates of a point where we want to find objects</param>
        /// <param name="filter">Determines whether a figure should be included in hit-testing</param>
        /// <returns>A figure with topmost ZIndex that is under the point
        /// and for which the filter is true. Returns null if nothing is found.</returns>
        public virtual IFigure HitTest(System.Windows.Point point, Predicate<IFigure> filter)
        {
            IFigure bestFoundSoFar = null;

            foreach (var item in this.Where(f => f is FigureList || filter(f)))
            {
                IFigure found = item.HitTest(point);
                if (found != null)
                {
                    if (bestFoundSoFar == null || bestFoundSoFar.ZIndex <= found.ZIndex)
                    {
                        bestFoundSoFar = found;
                    }
                }
            }
            return bestFoundSoFar;
        }

        public ReadOnlyCollection<IFigure> HitTestMany(Point point)
        {
            List<IFigure> result = new List<IFigure>();

            foreach (var item in this)
            {
                IFigure found = item.HitTest(point);
                if (found != null && found.Visible)
                {
                    result.Add(found);
                }
            }

            if (result.Count > 0)
            {
                result.Sort((f1, f2) => f1.ZIndex.CompareTo(f2.ZIndex));
            }

            return result.AsReadOnly();
        }

        /// <summary>
        /// Finds a figure of a given type at the point. Collections are not searched.
        /// </summary>
        public IFigure HitTestNoCollections(System.Windows.Point point, Type figureType)
        {
            IFigure bestFoundSoFar = null;

            foreach (var item in this)
            {
                IFigure found = item.HitTest(point);
                if (found != null && figureType.IsAssignableFrom(found.GetType()))
                {
                    if (bestFoundSoFar == null || bestFoundSoFar.ZIndex <= found.ZIndex)
                    {
                        bestFoundSoFar = found;
                    }
                }
            }
            return bestFoundSoFar;
        }

        #endregion

        public virtual void OnAddingToCanvas(Canvas newContainer)
        {
            this.ForEach(f => f.OnAddingToCanvas(newContainer));
        }

        public virtual void OnRemovingFromCanvas(Canvas leavingContainer)
        {
            this.ForEach(f => f.OnRemovingFromCanvas(leavingContainer));
        }

        public int ZIndex { get; set; }
        public virtual bool Visible { get; set; }
        public bool Enabled { get; set; }

        public override string ToString()
        {
            StringBuilder s = new StringBuilder();
            foreach (var item in this)
            {
                DumpFigure(item, s);
            }
            return s.ToString();
        }

        private void DumpFigure(IFigure item, StringBuilder s)
        {
            s.AppendLine(item.ToString());
            string tab = "   ";
            if (!item.Dependencies.IsEmpty())
            {
                int i = 0;
                foreach (var dependency in item.Dependencies)
                {
                    s.AppendLine(tab + i++.ToString() + ". " + dependency.ToString());
                }
            }
            if (!item.Dependents.IsEmpty())
            {
                foreach (var dependent in item.Dependents)
                {
                    s.AppendLine(tab + dependent.ToString());
                }
            }
        }

        public string Name { get; set; }
        public bool Selected { get; set; }
        public bool Locked { get; set; }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            new DrawingSerializer().WriteFigureList(this, writer);
        }

        public void ReadXml(System.Xml.Linq.XElement element)
        {
            new DrawingDeserializer().ReadFigureList(this, element);
        }

        public bool Equals(IFigure other)
        {
            return object.ReferenceEquals(this, other);
        }

        public IFigureStyle Style
        {
            get
            {
                return null;
            }
            set
            {
            }
        }

        public string GenerateFigureName()
        {
            return this.GenerateNewName();
        }

        public bool Contains(string name)
        {
            return this.GetAllFiguresRecursive().Where(f => f.Name == name).Any();
        }

        public void OnAddingToDrawing(Drawing drawing)
        {
            foreach (var item in this)
            {
                item.OnAddingToDrawing(drawing);
            }
        }

        public void OnRemovingFromDrawing(Drawing drawing)
        {
            foreach (var item in this)
            {
                item.OnRemovingFromDrawing(drawing);
            }
        }
    }
}